package model

import (
	"My-Blog/utils/errmsg"
	"crypto/rand"
	"encoding/base64"
	"errors"
	"golang.org/x/crypto/scrypt"
	"gorm.io/gorm"
	"log"
)

const KeyLen = 10 //指定最终生成的哈希值长度（字节）
const SaltLen = 8

type User struct {
	//gorm.Model 是 GORM 库中定义的一个基础模型结构体，
	//包含了一些常用的字段，用于被其他模型结构体嵌入
	gorm.Model
	Username string `gorm:"type: varchar(20);not null" json:"username" validate:"required,min=4,max=12" label:"用户名"`
	Password string `gorm:"type: varchar(20);not null " json:"password" validate:"required,min=6,max=20"label:"密码"`
	Role     int    `gorm:"type:int;DEFAULT:2" json:"role"validate:"required,gte=2"label:"角色码"`
}

// 对数据库的操作
// 查询用户是否存在
func CheckUser(name string) (code int) {
	var users User
	db.Select("id").Where("username = ?", name).First(&users)
	if users.ID > 0 {
		return errmsg.ERROR_USERNAME_USED //1001
	}
	return errmsg.SUCCSE
}

// 新增用户
func CreateUser(data *User) int {
	//data.Password = ScryptPwd(data.Password)
	err := db.Create(&data).Error
	if err != nil {
		return errmsg.ERROR //500
	}
	return errmsg.SUCCSE
}

// 查询用户列表
func GetUsers(pageSize int, pageNum int) ([]User, int64) {
	var users []User
	var total int64
	err = db.Limit(pageSize).Offset((pageNum - 1) * pageSize).Find(&users).Count(&total).Error
	if err != nil && err != gorm.ErrRecordNotFound {
		return nil, 0
	}
	return users, total
}

// 编辑用户
func EditUser(id int, data *User) int {
	var user User
	var maps = make(map[string]interface{})
	maps["username"] = data.Username
	maps["role"] = data.Role
	err := db.Model(&user).Where("id = ?", id).Updates(maps).Error
	if err != nil {
		return errmsg.ERROR
	}
	return errmsg.SUCCSE
}

// 删除用户
func DeleteUser(id int) int {
	var user User
	err := db.Where("id = ?", id).Delete(&user).Error
	if err != nil && err != gorm.ErrRecordNotFound {
		return errmsg.ERROR
	}
	return errmsg.SUCCSE
}

// 钩子函数
// 注意方法和钩子函数的区别，方法得需要手动调用，跟钩子函数不需要手动调用
func (u *User) BeforeSave(tx *gorm.DB) error {
	u.Password = ScryptPwd(u.Password) //用于进行密码加密
	return nil
}

// 密码加密
func ScryptPwd(password string) string {

	//1.生成随机盐值（8字节固定值）
	salt, err := generateSalt(SaltLen)
	//密码加密的核心逻辑：
	//[]byte(password)：明文密码转换为字节切片
	//16384：N（CPU / 内存成本因子），值越大，计算越耗时（抗暴力破解能力越强）
	//2. HashPw 是加密后的 10字节哈希值（字节切片）
	HashPw, err := scrypt.Key(
		[]byte(password), //明文密码转字节切片
		salt,             //随机盐值（与密码混合计算，防彩虹表）
		16384,            // N：成本因子（2^14），控制迭代次数，值越大加密越慢（抗暴力破解）
		8,                // r：块大小因子，控制内存占用（通常设为8）
		1,                // p：并行因子，控制并行计算量（通常设为1）
		KeyLen)           // 最终生成的哈希值长度（10字节）
	if err != nil {
		log.Fatal(err) // 加密失败直接终止程序（生产环境建议返回错误，而非终止）
	}
	//3.拼接盐值和哈希值：盐值（前8字节）+ 哈希值（后10字节）
	combined := append(salt, HashPw...) // ... 是切片展开运算符，将HashPw的元素逐个追加
	//通过 base64.StdEncoding.EncodeToString 转换为字符串（如 EjRWeJq8dQ==），方便存储到数据库或传输。
	//将18字节的二进制数据转为字符串（方便存储到数据库）
	Fpwd := base64.StdEncoding.EncodeToString(combined)
	return Fpwd
}

// 生成随机盐值
func generateSalt(length int) ([]byte, error) {
	salt := make([]byte, length) //创建指定长度的空字节切片
	_, err := rand.Read(salt)    //用 crypto/rand 填充真随机字节（加密安全，不可预测）
	if err != nil {
		return nil, err
	}
	return salt, nil
}

// 从存储的加密密码中提取盐值和哈希值
func extractSaltAndHash(encoded string) (salt, hash []byte, err error) {

	// 1. Base64解码存储的密码
	combined, err := base64.StdEncoding.DecodeString(encoded)
	if err != nil {
		return nil, nil, err
	}
	// 2. 拆分：前8字节是盐值，后10字节是哈希值（与ScryptPwd的拼接顺序一致）
	if len(combined) != SaltLen+KeyLen { // SaltLen=8, KeyLen=10，共18字节
		return nil, nil, errors.New("invalid password format")
	}
	salt = combined[:SaltLen] // 前8字节：盐值
	hash = combined[SaltLen:] // 后10字节：哈希值
	return salt, hash, nil
}

// 登录验证
func CheckLogin(username, password string) int {

	var user User
	// 步骤1：查询数据库，确认用户是否存在
	db.Where("username = ?", username).First(&user)
	if user.ID == 0 {
		return errmsg.ERROR_USER_NOT_EXIST
	}
	// 2. 从数据库存储的密码中提取盐值和原始哈希值
	salt, storedHash, err := extractSaltAndHash(user.Password)
	if err != nil {
		return errmsg.ERROR_PASSWORD_WRONG
	}
	// 3. 用提取的盐值重新加密输入的密码
	inputHash, err := scrypt.Key([]byte(password), salt, 16384, 8, 1, KeyLen)
	if err != nil {
		return errmsg.ERROR
	}
	// 4. 对比加密后的输入哈希与数据库存储的哈希
	if string(inputHash) != string(storedHash) {
		return errmsg.ERROR_PASSWORD_WRONG // 密码错误
	}
	if user.Role != 3 {
		return errmsg.ERROR_USER_NO_RIGHT
	}
	return errmsg.SUCCSE
}
